ECharts 3.0底层zrender 3.x源码分析2-Painter(V层)

上一篇介绍了zrender的总体结构,这一篇我们就详细介绍View层–Painter(Painter.js)。

一些demo和没有在博客中介绍的源码请进我的github仓库

https://github.com/zrysmt/echarts3/tree/master/zrender

Painter利用canvas负责真正的绘图操作。

  • 1.负责canvas及其周边DOM元素的创建与处理
  • 2.负责调用各个Shape(预定义好的)进行绘制
  • 3.提供基本的操作方法,渲染(render)、刷新(refresh)、尺寸变化(resize)、擦除(clear)等

1.渲染结构分析

两个例子都是渲染到div上。

1
<div id="main" style="width:1000px;height:600px;margin:0;"></div>

zrender 3.x版本渲染结果(demo/demo1/demo3-chart.html)

我们可以看到渲染结果都会新建一层div(从下面的分析我们可以得到这个div就是_domRoot),里面嵌套canvas。如果有使用addHover(有hover层,data-zr-dom-id=”zr_100000”)的话,hover层会单独列一个canvas画布。

1
2
3
4
5
6
7
8
9
10
11
sector.on('mouseover', function() {
zr.addHover(this, {
stroke: 'yellow',
lineWidth: 10,
opacity: 1
});
zr.refresh();
});
sector.on('mouseout', function() {
zr.removeHover(this);
});

1
2
3
4
5
6
<div id="main" style="width: 1000px; height: 600px; margin: 0px; -webkit-tap-highlight-color: transparent; user-select: none;">
<div style="position: relative; overflow: hidden; width: 1000px; height: 600px; padding: 0px; margin: 0px; border-width: 0px; cursor: default;">
<canvas width="1000" height="600" data-zr-dom-id="zr_0" style="position: absolute; left: 0px; top: 0px; width: 1000px; height: 600px; user-select: none; -webkit-tap-highlight-color: rgba(0, 0, 0, 0); padding: 0px; margin: 0px; border-width: 0px;"></canvas>
<canvas width="1000" height="600" data-zr-dom-id="zr_100000" style="position: absolute; left: 0px; top: 0px; width: 1000px; height: 600px; user-select: none; -webkit-tap-highlight-color: rgba(0, 0, 0, 0); padding: 0px; margin: 0px; border-width: 0px;"></canvas>
</div>
</div>

2.构造函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
var Painter = function (root, storage, opts) {
// In node environment using node-canvas
var singleCanvas = !root.nodeName // In node ?
|| root.nodeName.toUpperCase() === 'CANVAS';
this._opts = opts = util.extend({}, opts || {});
this.dpr = opts.devicePixelRatio || config.devicePixelRatio;
this._singleCanvas = singleCanvas;
/**
* 绘图容器
* @type {HTMLElement}
*/
this.root = root;
var rootStyle = root.style;
if (rootStyle) {
rootStyle['-webkit-tap-highlight-color'] = 'transparent';
rootStyle['-webkit-user-select'] =
rootStyle['user-select'] =
rootStyle['-webkit-touch-callout'] = 'none';
root.innerHTML = '';
}
/**
* @type {module:zrender/Storage}
*/
this.storage = storage;
/**
* 存储图层画布,这个变量很重要
* @type {Array.<number>}
* @private
*/
var zlevelList = this._zlevelList = [];

/** 图层
* @type {Object.<string, module:zrender/Layer>}
* @private
*/
var layers = this._layers = {};
this._layerConfig = {};

if (!singleCanvas) {//没有画布,就使用div
this._width = this._getSize(0);
this._height = this._getSize(1);

var domRoot = this._domRoot = createRoot(
this._width, this._height
);
root.appendChild(domRoot);
}
else {//已经有块画布
// Use canvas width and height directly
var width = root.width;
var height = root.height;
this._width = width;
this._height = height;

// Create layer if only one given canvas
// dpr设置为1,是因为canvas已经定了宽和高
var mainLayer = new Layer(root, this, 1);
mainLayer.initContext();
// FIXME Use canvas width and height
// mainLayer.resize(width, height);
layers[0] = mainLayer;
zlevelList.push(0);
}

this.pathToImage = this._createPathToImage();
// Layers for progressive rendering
this._progressiveLayers = [];
this._hoverlayer;
this._hoverElements = [];
};

3.Painter.prototype

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
Painter.prototype = {
constructor: Painter,
isSingleCanvas: function() {},
getViewportRoot: function() {},
refresh: function(paintAll) {},
addHover: function(el, hoverStyle) {},
removeHover: function(el) {},
clearHover: function(el) {},
refreshHover: function() {},
_startProgessive: function() {},
_clearProgressive: function() {},
_paintList: function(list, paintAll) {},
_doPaintList: function(list, paintAll) {},
_doPaintEl: function(el, currentLayer, forcePaint, scope) {},
getLayer: function(zlevel) {},
insertLayer: function(zlevel, layer) {},
eachLayer: function(cb, context) {},
eachBuildinLayer: function(cb, context) {},
eachOtherLayer: function(cb, context) {},
getLayers: function() {},
_updateLayerStatus: function(list) {},
clear: function() {},
_clearLayer: function(layer) {},
configLayer: function(zlevel, config) {},
delLayer: function(zlevel) {},
resize: function(width, height) {},
clearLayer: function(zlevel) {},
dispose: function() {},
getRenderedCanvas: function(opts) {},
getWidth: function() {},
getHeight: function() {},
_getSize: function(whIdx) {},
_pathToImage: function(id, path, width, height, dpr) {},
_createPathToImage: function() {}
}

我们再来回顾下整个渲染的过程:
add(zrender.js)–>addRoot(Storage.js) –> addToMap(Storage.js) –>
dirty[标记为脏的,下一帧渲染] (path.js) –> refresh(Painter.js)–>_paintList[遍历_displayList] (Painter.js)–>
_doPaintEl[渲染单个元素] Painter.js) –>brush(Path.js)–>buildPath (各个类型的shape)

  • refresh刷新,刷新去绘制
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* 刷新
* @param {boolean} [paintAll=false] 强制绘制所有displayable
*/
refresh: function(paintAll) {
var list = this.storage.getDisplayList(true); //要绘制的图形
var zlevelList = this._zlevelList;
this._paintList(list, paintAll); //去绘制
// Paint custum layers 绘制layer层
for (var i = 0; i < zlevelList.length; i++) {
var z = zlevelList[i];
var layer = this._layers[z];
if (!layer.isBuildin && layer.refresh) {
layer.refresh();
}
}
this.refreshHover(); //刷新hover层
if (this._progressiveLayers.length) {
this._startProgessive();
}
return this;
}
  • _paintList
1
2
3
4
5
6
7
8
9
10
_paintList: function(list, paintAll) {
if (paintAll == null) {
paintAll = false;
}
this._updateLayerStatus(list);
this._clearProgressive();
this.eachBuildinLayer(preProcessLayer);
this._doPaintList(list, paintAll); //全部标注为脏的渲染【dirty(false)】
this.eachBuildinLayer(postProcessLayer);
}
  • _doPaintList

注意这里已经遍历了(遍历的是_displayList数组),所以后面的只针对单个元素绘制即可。

1
2
3
4
5
//... ...
for (var i = 0, l = list.length; i < l; i++) {
//... ...
this._doPaintEl(el, currentLayer, paintAll, scope);//绘制每个元素
}

  • _doPaintEl
1
2
//... ...
el.brush(ctx, scope.prevEl || null);//在Path.js中的方法brush

4.分析Painter对象

这一系列的操作是:

  • 创建canvas外层包裹着_domRoot(div)
  • canvas要绘制的东西都存储在storage中的_displayList数组中
  • 遍历_displayList
  • 最后调用buildPath的canvas绘制。

5.Hover图层

如第1部分所见,如果增加了hover层(addHOver方法),那么会增加一层canvas,现在就来看这一层canvas是如何作用的。
addHover(zrender.js)–>

  • addHover(zrender.js)
1
2
3
4
5
6
addHover: function(el, style) {
if (this.painter.addHover) {
this.painter.addHover(el, style);
this.refreshHover();
}
}
  • addHover(Painter.js)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
addHover: function(el, hoverStyle) {
if (el.__hoverMir) {
return;
}
var elMirror = new el.constructor({
style: el.style,
shape: el.shape
});
elMirror.__from = el;
el.__hoverMir = elMirror;
elMirror.setStyle(hoverStyle);
this._hoverElements.push(elMirror);
//存放到this._hoverElements(数组)
}

我们在第三部分已经看到refresh方法中的refreshHover,渲染canvas时候,会渲染两个canvas,一个是主canvas,一个是hover层canvas,第二个canvas就是使用refreshHover方法。

  • refreshHover(Painter.js)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
refreshHover: function() {
var hoverElements = this._hoverElements;
var len = hoverElements.length;
var hoverLayer = this._hoverlayer;
hoverLayer && hoverLayer.clear();
if (!len) {
return;
}
timsort(hoverElements, this.storage.displayableSortFunc);
if (!hoverLayer) {//不存在则会新创建一层canvas
hoverLayer = this._hoverlayer = this.getLayer(1e5);
}

var scope = {};
hoverLayer.ctx.save();
for (var i = 0; i < len;) {
var el = hoverElements[i];
var originalEl = el.__from;
if (!(originalEl && originalEl.__zr)) {
hoverElements.splice(i, 1);
originalEl.__hoverMir = null;
len--;
continue;
}
i++;
if (!originalEl.invisible) {
el.transform = originalEl.transform;
el.invTransform = originalEl.invTransform;
el.__clipPaths = originalEl.__clipPaths;
// el.
this._doPaintEl(el, hoverLayer, true, scope);//同第3部分
}
}
hoverLayer.ctx.restore();
}

参考阅读: